using DevExpress.ExpressApp;
using DevExpress.ExpressApp.Blazor.Services;
using EasyXaf.EntityDesigner.BusinessObjects;
using EasyXaf.EntityDesigner.BusinessObjects.Entities;
using EasyXaf.EntityDesigner.BusinessObjects.Entities.Associations;
using EasyXaf.EntityDesigner.Commands;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using System.Collections.Concurrent;
using System.Text.Json.Nodes;

namespace EasyXaf.EntityDesigner.Editors.EntityAssociationEditors;

public sealed partial class EntityAssociationRenderer : IAsyncDisposable
{
    private readonly string ViewportEleId = $"id-{Guid.NewGuid()}";
    private readonly string CommandLineEleId = $"id-{Guid.NewGuid()}";

    private CommandManager _commandManager;

    private bool AllowRefreshEditor { get; set; } = true;

    private IList<Guid> SelectedEntityIds { get; } = [];

    private readonly IList<Entity> Entities = [];

    private IObjectSpace ObjectSpace => Model.ObjectSpace;

    private DomainModel DomainModel => ObjectSpace.GetObjectByKey<DomainModel>(Model.DomainModelId);

    private EntityUpdater EntityUpdater { get; } = new();

    private CommandManager CommandManager
    {
        get => _commandManager ??= ActivatorUtilities.CreateInstance<CommandManager>(ServiceProvider, DomainModel);
    }

    private DotNetObjectReference<EntityAssociationRenderer> DotNetObjectRef { get; set; }

    private IJSObjectReference JsModule { get; set; }

    private ConcurrentBag<Action> Actions { get; set; } = [];

    [Parameter]
    public EntityAssociationModel Model { get; set; }

    [Inject]
    private IJSRuntime JSRuntime { get; set; }

    [Inject]
    private IServiceProvider ServiceProvider { get; set; }

    [Inject]
    private IXafApplicationProvider ApplicationProvider { get; set; }

    private void EnableRefreshEditor()
    {
        AllowRefreshEditor = true;
    }

    private void DisableRefreshEditor()
    {
        AllowRefreshEditor = false;
    }

    private bool LoadEntities()
    {
        var args = new QueryCollectionSourceEventArgs();
        Model.RaiseQueryCollectionSource(args);

        Entities.Clear();

        if (args.CollectionSource != null)
        {
            foreach (var entity in args.CollectionSource)
            {
                Entities.Add(entity);
            }
            return true;
        }

        return false;
    }

    private void SelectEntities(params Entity[] entities)
    {
        SelectedEntityIds.Clear();

        foreach (var entity in entities)
        {
            SelectedEntityIds.Add(entity.Oid);
        }

        Model.RaiseSelectionChanged(entities);
    }

    private void BatchCommand()
    {
        var application = ApplicationProvider.GetApplication();
        var objectSpace = application.CreateObjectSpace(typeof(BatchCommandObject));
        var batchCommandObject = objectSpace.CreateObject<BatchCommandObject>();
        var viewId = application.GetDetailViewId(typeof(BatchCommandObject));
        var view = application.CreateDetailView(objectSpace, viewId, true, batchCommandObject);
        application.ShowViewStrategy.ShowViewInPopupWindow(view, () =>
        {
            var command = batchCommandObject.Command.Trim();
            var lines = command.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);

            try
            {
                CommandManager.ExecuteCommands(lines, new CommandContext
                {
                    ViewportBounds = Model.ViewportBounds,
                    CurrentEntity = Entities.FirstOrDefault(e => e.Oid == SelectedEntityIds.FirstOrDefault()) ?? null,
                });
            }
            catch (Exception ex)
            {
                throw new UserFriendlyException(ex);
            }

            ObjectSpace.Refresh();
        });
    }

    private void Model_RefreshRequested(object sender, EventArgs e)
    {
        if (LoadEntities())
        {
            if (AllowRefreshEditor)
            {
                Actions.Add(async () => await JsModule.InvokeVoidAsync("refresh", ViewportEleId));
            }

            if (SelectedEntityIds.Any())
            {
                SelectEntities(Entities.Where(e => SelectedEntityIds.Contains(e.Oid)).ToArray());
            }

            InvokeAsync(StateHasChanged);
        }
    }

    private void Model_UnselectAllRequested(object sender, EventArgs e)
    {
        SelectedEntityIds.Clear();
        InvokeAsync(StateHasChanged);
    }

    protected override void OnInitialized()
    {
        base.OnInitialized();

        Model.RefreshRequested += Model_RefreshRequested;
        Model.UnselectAllRequested += Model_UnselectAllRequested;

        LoadEntities();
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await base.OnAfterRenderAsync(firstRender);

        if (firstRender)
        {
            if (Model.DomainModelId == Guid.Empty)
            {
                ServiceProvider.GetService<NavigationManager>().NavigateTo("/DomainModel_ListView");
            }

            var jsModuleFile = "/Editors/EntityAssociationEditors/EntityAssociationRenderer.razor.js";
            JsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", jsModuleFile);
            DotNetObjectRef = DotNetObjectReference.Create(this);
            await JsModule.InvokeVoidAsync("init", ViewportEleId, CommandLineEleId, DotNetObjectRef);
        }

        if (Actions.Any())
        {
            foreach (var action in Actions)
            {
                action.Invoke();
            }
            Actions.Clear();
        }
    }

    [JSInvokable]
    public Task EntityMovedAsync(string id, double left, double top)
    {
        var entity = Entities.FirstOrDefault(e => e.Oid.ToString() == id);
        if (entity != null)
        {
            var entityDesign = entity.GetEntityDesign(DomainModel);
            if (entityDesign != null)
            {
                if ((int)entityDesign.Left != (int)left || (int)entityDesign.Top != (int)top)
                {
                    entityDesign.Left = left;
                    entityDesign.Top = top;

                    DisableRefreshEditor();

                    ObjectSpace.CommitChanges();

                    EnableRefreshEditor();
                }
            }
        }
        return Task.CompletedTask;
    }

    [JSInvokable]
    public async Task SelectEntitiesAsync(string[] entityIds)
    {
        SelectEntities(Entities.Where(e => entityIds.Contains(e.Oid.ToString())).ToArray());
        await InvokeAsync(StateHasChanged);
    }

    [JSInvokable]
    public async Task NodesLayoutAsync(JsonArray nodes)
    {
        foreach (var node in nodes.OfType<JsonObject>())
        {
            if (node.TryGetPropertyValue("id", out var id))
            {
                if (node.TryGetPropertyValue("x", out var x))
                {
                    if (node.TryGetPropertyValue("y", out var y))
                    {
                        await EntityMovedAsync(id.ToString(), x.GetValue<double>(), y.GetValue<double>());
                    }
                }
            }
        }
    }

    [JSInvokable]
    public Task ConnectEntityAsync(string sourceEntityId, string sourcePropertyId, string targetEntityId, string targetPropertyId)
    {
        var sourceEntity = Entities.FirstOrDefault(e => e.Oid.ToString() == sourceEntityId);
        var targetEntity = Entities.FirstOrDefault(e => e.Oid.ToString() == targetEntityId);

        if (sourceEntity == null || targetEntity == null)
        {
            return Task.CompletedTask;
        }

        var sourceProperty = sourceEntity.Properties.FirstOrDefault(p => p.Oid.ToString() == sourcePropertyId);
        var targetProperty = targetEntity.Properties.FirstOrDefault(p => p.Oid.ToString() == targetPropertyId);

        var application = ApplicationProvider.GetApplication();
        var objectSpace = application.CreateObjectSpace(typeof(Association));

        sourceEntity = objectSpace.GetObject(sourceEntity);
        targetEntity = objectSpace.GetObject(targetEntity);
        sourceProperty = sourceProperty != null ? objectSpace.GetObject(sourceProperty) : null;
        targetProperty = targetProperty != null ? objectSpace.GetObject(targetProperty) : null;

        var association = sourceEntity.CreateAssociation(sourceEntity, sourceProperty, targetEntity, targetProperty);

        var viewId = application.GetDetailViewId(typeof(Association));
        var view = application.CreateDetailView(objectSpace, viewId, true, association);

        void ObjectSpace_Committed(object sender, EventArgs e)
        {
            ObjectSpace.Refresh();
        }

        application.ShowViewStrategy.ShowViewInPopupWindow(view, () =>
        {
            objectSpace.SetModified(association);
            objectSpace.Committed -= ObjectSpace_Committed;
            objectSpace.Committed += ObjectSpace_Committed;
        });

        return Task.CompletedTask;
    }

    [JSInvokable]
    public Task OpenAssociationAsync(string associationId)
    {
        var application = ApplicationProvider.GetApplication();
        var objectSpace = application.CreateObjectSpace(typeof(Association));
        var association = objectSpace.GetObjectByKey<Association>(Guid.Parse(associationId));
        var viewId = application.GetDetailViewId(typeof(Association));
        var view = application.CreateDetailView(objectSpace, viewId, true, association);

        void ObjectSpace_Committed(object sender, EventArgs e)
        {
            ObjectSpace.Refresh();
        }

        application.ShowViewStrategy.ShowViewInPopupWindow(view, () =>
        {
            objectSpace.SetModified(association);
            objectSpace.Committed -= ObjectSpace_Committed;
            objectSpace.Committed += ObjectSpace_Committed;
        });

        return Task.CompletedTask;
    }

    [JSInvokable]
    public Task DeleteAssociationAsync(string associationId)
    {
        var association = ObjectSpace.GetObjectByKey<Association>(Guid.Parse(associationId));

        DisableRefreshEditor();

        ObjectSpace.Delete(association);
        ObjectSpace.CommitChanges();

        EnableRefreshEditor();

        return Task.CompletedTask;
    }

    [JSInvokable]
    public Task UpdateViewportBoundsAsync(double left, double top, double width, double height)
    {
        Model.ViewportBounds = [left, top, width, height];
        return Task.CompletedTask;
    }

    [JSInvokable]
    public async Task<bool> ExecuteCommandAsync(string command)
    {
        command = command?.Trim();

        if (string.IsNullOrWhiteSpace(command))
        {
            return false;
        }

        ICommand executedCommand = null;

        var commandContext = new CommandContext
        {
            ViewportBounds = Model.ViewportBounds,
            CurrentEntity = Entities.FirstOrDefault(e => e.Oid == SelectedEntityIds.FirstOrDefault()) ?? null,
        };

        try
        {
            executedCommand = CommandManager.ExecuteCommand(command, commandContext);
        }
        catch (Exception ex)
        {
            ApplicationProvider.GetApplication().ShowViewStrategy.ShowMessage(ex.Message, InformationType.Error);
            return false;
        }

        if (executedCommand is SelectEntityCommand)
        {
            SelectEntities(ObjectSpace.GetObject(commandContext.CurrentEntity));
            await InvokeAsync(StateHasChanged);
        }
        else if (executedCommand is CreateEntityPropertyCommand)
        {
            EntityUpdater.RaiseEntityUpdated((Entity)ObjectSpace.ReloadObject(commandContext.CurrentEntity));
        }
        else if (executedCommand is CreateEntityCommand || executedCommand is CreateAssociationCommand)
        {
            ObjectSpace.Refresh();

            if (executedCommand is CreateEntityCommand)
            {
                SelectEntities(ObjectSpace.GetObject(commandContext.CurrentEntity));
                await InvokeAsync(StateHasChanged);
            }
        }

        return true;
    }

    public async ValueTask DisposeAsync()
    {
        if (Model != null)
        {
            Model.RefreshRequested -= Model_RefreshRequested;
            Model.UnselectAllRequested -= Model_UnselectAllRequested;
        }

        try
        {
            if (JsModule != null)
            {
                await JsModule.InvokeVoidAsync("dispose", ViewportEleId);
                await JsModule.DisposeAsync();
                JsModule = null;
            }
        }
        catch (JSDisconnectedException)
        {
        }
    }
}